use std::collections::BTreeMap;

use ruma::{
    api::client::{error::ErrorKind, message::send_message_event},
    events::MessageLikeEventType,
};
use serde_json::from_str;

use crate::{core::pdu::PduBuilder, server::server, utils, Error, Result, Ruma};

/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
///
/// Send a message event into the room.
///
/// - Is a NOOP if the txn id was already used before and returns the same event
///   id again
/// - The only requirement for the content is that it has to be valid json
/// - Tries to send the event into the room, auth rules will determine if it is
///   allowed
///

pub(crate) async fn send_message_event_route(
    body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
    let services = server();
    let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    let sender_device = body.sender_device.as_deref();

    let state_lock = services.rooms.state.mutex.clone().lock_owned().await;

    // Forbid m.room.encrypted if encryption is disabled
    if MessageLikeEventType::RoomEncrypted == body.event_type && !services.global.allow_encryption()
    {
        return Err(Error::BadRequest(
            ErrorKind::forbidden(),
            "Encryption has been disabled",
        ));
    }

    // Check if this is a new transaction id
    if let Some(response) =
        services
            .transaction_ids
            .existing_txnid(sender_user, sender_device, &body.txn_id)?
    {
        // The client might have sent a txnid of the /sendToDevice endpoint
        // This txnid has no response associated with it
        if response.is_empty() {
            return Err(Error::BadRequest(
                ErrorKind::InvalidParam,
                "Tried to use txn id already used for an incompatible endpoint.",
            ));
        }

        let event_id = utils::string::string_from_bytes(&response)
            .map_err(|_| Error::bad_database("Invalid txnid bytes in database."))?
            .try_into()
            .map_err(|_| Error::bad_database("Invalid event id in txnid data."))?;
        return Ok(send_message_event::v3::Response { event_id });
    }

    let mut unsigned = BTreeMap::new();
    unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());

    let event_id = services
        .rooms
        .timeline
        .build_and_append_pdu(
            PduBuilder {
                event_type: body.event_type.to_string().into(),
                content: from_str(body.body.body.json().get())
                    .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
                unsigned: Some(unsigned),
                state_key: None,
                redacts: None,
                timestamp: None,
            },
            sender_user,
            &body.room_id,
            &state_lock,
        )
        .await?;

    services.transaction_ids.add_txnid(
        sender_user,
        sender_device,
        &body.txn_id,
        event_id.as_bytes(),
    )?;

    drop(state_lock);

    Ok(send_message_event::v3::Response::new(
        (*event_id).to_owned(),
    ))
}
